#include "stdafx.h"
#include "VectorBatch.h"

#include "ErrorHandlers.h"
#include "Exception.h"
#include "Filesystem.h"
#include "Engine.h"

#define VERTEX_BUFFER_NAME "VectorBatchVB"
#define DEFAULT_EFFECT_NAME "VectorBatch"
#define DEFAULT_EFFECT_FILE  "VectorBatch.fx"

namespace Cvekas {

int VectorBatch::vertex_buffer_name_counter = 0;

Triangle::Triangle() 
{
}

Triangle::Triangle(const Triangle& t)
: p1(t.p1), p2(t.p2), p3(t.p3), color(t.color), layer(t.layer)
{
}

bool Triangle::operator<(const Triangle& t)
{
	return layer < t.layer;
}

VectorBatch::VectorBatch(RendererPtr renderer, uint virtual_width, uint virtual_height, uint16 base_priority)
: renderer(renderer), virtual_width(virtual_width), virtual_height(virtual_height), 
  base_priority(base_priority), triangle_count(0), span_count(0), presented(false)
{
	Engine e;

	EffectManagerPtr effect_manager = e.getVideoDevice()->getEffectManager();
	GpuBufferManagerPtr buffer_manager = e.getVideoDevice()->getGpuBufferManager();

	// Find or create default effect
	if(effect_manager->exists(DEFAULT_EFFECT_NAME))
		effect = effect_manager->get(DEFAULT_EFFECT_NAME);
	else
		effect = effect_manager->load(DEFAULT_EFFECT_NAME, Filesystem::redirect(DEFAULT_EFFECT_FILE));
	
	// Create vertex buffer
	std::string vb_name = FORMAT(VERTEX_BUFFER_NAME "%d", % vertex_buffer_name_counter);
	vertex_buffer_name_counter++;

	vertex_buffer = buffer_manager->createVertexBuffer(vb_name, VertexPosColor::getFormat(), max_triangles * 3, true);
}

VectorBatch::~VectorBatch()
{
}

void VectorBatch::present()
{
	if(presented)
		throw EXCEPTION("Already presented this frame!");

	build_batches();
	presented = true;
	renderer->prepare(*this);
}

void VectorBatch::setEffect(EffectPtr effect)
{
	this->effect = effect;
}

void VectorBatch::drawLine(uint layer, const Vector2& start, const Vector2& end, float width, const Color& color)
{
	Vector2 normal(end.y - start.y, start.x - end.x);
	normal.normalize();
	normal *= width / 2.0f;

	Vector2 p1 = start + normal;
	Vector2 p2 = start - normal;
	Vector2 p3 = end + normal;
	Vector2 p4 = end - normal;

	drawTriangle(layer, p2, p1, p3, color);
	drawTriangle(layer, p3, p4, p2, color);
}

// TODO: Fix line connection points
void VectorBatch::drawLineStrip(uint layer, const std::vector<Vector2>& points, float width, const Color& color)
{
	if(points.size() < 2)
		throw EXCEPTION("Line strip must have atleast 2 points");

	for(uint i = 1; i < points.size(); ++i)
		drawLine(layer, points[i-1], points[i], width, color);
}

void VectorBatch::drawTriangle(uint layer, const Vector2& p1, const Vector2& p2, const Vector2& p3, const Color& color)
{
	if(triangle_count >= max_triangles - 1)
		throw EXCEPTION("Too many triangles!");

	float w = (float)virtual_width;
	float h = (float)virtual_height;
	triangles[triangle_count].p1 = Vector2((p1.x / w) * 2.0f - 1.0f, (p1.y / h) * -2.0f + 1.0f);
	triangles[triangle_count].p2 = Vector2((p2.x / w) * 2.0f - 1.0f, (p2.y / h) * -2.0f + 1.0f);
	triangles[triangle_count].p3 = Vector2((p3.x / w) * 2.0f - 1.0f, (p3.y / h) * -2.0f + 1.0f);
	triangles[triangle_count].color = color;
	triangles[triangle_count].layer = layer;
	triangle_count++;
}

// Polygon triangulation helper methods:

bool pointInsideTriangle(const Vector2& p1, const Vector2& p2, const Vector2& p3, const Vector2& p)
{
	Vector2 a = p3 - p2;
	Vector2 b = p1 - p3;
	Vector2 c = p2 - p1;
	Vector2 ap = p - p1;
	Vector2 bp = p - p2;
	Vector2 cp = p - p3;

	float abp = a.x*bp.y - a.y*bp.x;
	float cap = c.x*ap.y - c.y*ap.x;
	float bcp = b.x*cp.y - b.y*cp.x;

	return (abp >= 0.0f) && (cap >= 0.0f) && (bcp >= 0.0f);
}

bool canCut(const std::vector<Vector2>& points, uint ip1, uint ip2, uint ip3, uint* vertices, uint v_count)
{
	Vector2 p1 = points[vertices[ip1]];
	Vector2 p2 = points[vertices[ip2]];
	Vector2 p3 = points[vertices[ip3]];

	float area = (p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2.0f;
	if(area < epsilon)
		return false;

	for(uint j = 0; j < v_count; j+=3)
	{
		uint i = j;
		if(i == ip1 || i == ip2 || i == ip3)
			continue;
		
		if(pointInsideTriangle(p1, p2, p3, points[vertices[i]]))
			return false;
	}
	for(uint j = 1; j < v_count; j+=3)
	{
		uint i = j;
		if(i == ip1 || i == ip2 || i == ip3)
			continue;
		
		if(pointInsideTriangle(p1, p2, p3, points[vertices[i]]))
			return false;
	}
	for(uint j = 2; j < v_count; j+=3)
	{
		uint i = j;
		if(i == ip1 || i == ip2 || i == ip3)
			continue;
		
		if(pointInsideTriangle(p1, p2, p3, points[vertices[i]]))
			return false;
	}
	return true;
}

void VectorBatch::drawPolygon(uint layer, const std::vector<Vector2>& points, const Color& color)
{
	if(points.size() < 3)
		throw EXCEPTION("Polygon must have at least 3 points");

	if(points.size() == 3)
		drawTriangle(layer, points[0], points[1], points[2], color);

	// Triangulate polygon (based on http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml):

	uint vertex_count = points.size();
	uint* vertices = new uint[points.size()];
	for(uint i = 0; i < points.size(); ++i)
		vertices[i] = i;

	uint cuts_to_do = points.size() - 2;
	while(cuts_to_do > 0)
	{
		uint v1 = 0, v2 = 1, v3 = 2;
		while(!canCut(points, v1, v2, v3, vertices, vertex_count))
		{
			v1++; v2++; v3++;
			if(v1 >= vertex_count)
				throw EXCEPTION("Bad polygon");
			if(v2 >= vertex_count) v2 = 0;
			if(v3 >= vertex_count) v3 = 0;
		}

		drawTriangle(layer, points[vertices[v1]], points[vertices[v2]], points[vertices[v3]], color);
		for(uint i = v2; i+1 < vertex_count; ++i)
			vertices[i] = vertices[i+1];
		vertex_count--;
		cuts_to_do--;
	}

	delete[] vertices;
}

void VectorBatch::onPreRender(uint16 batch_id)
{
	// Does nothing, shouldn't be called at all
	assert(0);
}

uint VectorBatch::getBatches(Batch** batches)
{
	if(!presented)
		return 0;

	*batches = this->batches.data();
	presented = false;

	triangle_count = 0;

	return span_count;
}

void VectorBatch::build_batches()
{
	std::sort(triangles.begin(), triangles.begin() + triangle_count);

	// Build spans
	span_count = 0;
	uint i = 0;
	while(i < triangle_count)
	{
		uint span_length = 1;
		while(i + span_length < triangle_count && triangles[i].layer == triangles[i + span_length].layer)
		{
			span_length++;
		}

		spans[span_count].first = i;
		spans[span_count].count = span_length;
		span_count++;
		i += span_length;
	}

	if(span_count >= max_spans)
		throw EXCEPTION("Span buffer overflow!");

	// Fill vertex buffer
	LockedBuffer locked_vb = vertex_buffer->lock(false);
	assert(locked_vb.size == max_triangles * 3 * VertexPosColor::getSize());
	VertexPosColor* vertices = reinterpret_cast<VertexPosColor*>(locked_vb.data);

	for(uint i = 0; i < triangle_count; ++i)
	{
		Vector3 p1 = Vector3(triangles[i].p1.x, triangles[i].p1.y, 1.0f);
		Vector3 p2 = Vector3(triangles[i].p2.x, triangles[i].p2.y, 1.0f);
		Vector3 p3 = Vector3(triangles[i].p3.x, triangles[i].p3.y, 1.0f);

		D3DCOLOR color = (D3DCOLOR)triangles[i].color;
		vertices[3*i] = VertexPosColor(p1, color);
		vertices[3*i+1] = VertexPosColor(p2, color);
		vertices[3*i+2] = VertexPosColor(p3, color);
	}

	vertex_buffer->unlock();

	// Build batches
	for(uint i = 0; i < span_count; ++i)
	{
		Batch new_batch;
		assert(base_priority + triangles[spans[i].first].layer < MAX_UINT16);

		new_batch.priority = (uint16)(base_priority + triangles[spans[i].first].layer);
		new_batch.batch_id = 0;
		new_batch.vb_id = vertex_buffer->getId();
		new_batch.ib_id = VertexBuffer::INVALID_ID;
		new_batch.effect_id = effect->getId();
		new_batch.pass = 0;
		new_batch.start_index = (spans[i].first) * 3; 
		new_batch.tri_count = spans[i].count;
		new_batch.owner = NULL;

		batches[i] = new_batch;
	}
}

} // namespace